package com.notetool.util.tokenparser.semantic;

import java.util.HashMap;
import java.util.Iterator;

import com.notetool.util.tokenparser.Token;
import com.notetool.util.tokenparser.iterator.BackwardTokenIterator;
import com.notetool.util.tokenparser.iterator.ForwardTokenIterator;
import com.notetool.util.tokenparser.iterator.TokenIterator;
import com.notetool.util.tokenparser.syntax.Model;

public class NeighborTokenRestriction extends TokenRestriction
{
    
    ////////////////////
    // Members
    private HashMap<Model, Object> forbPreviousMdl;
	private HashMap<Model, Object> forbNextMdl;
	private HashMap<Model, Object> reqPreviousMdl;
	private HashMap<Model, Object> reqNextMdl;
	
	private HashMap<String, Object> forbPreviousCls;
    private HashMap<String, Object> forbNextCls;
    private HashMap<String, Object> reqPreviousCls;
    private HashMap<String, Object> reqNextCls;
    
	private HashMap<Model, Object> ignMdl;
	private HashMap<String, Object> ignCls;
	
    ////////////////////
    // Constructors
	public NeighborTokenRestriction ()
	{
	    forbPreviousMdl = new HashMap<Model, Object> ();
	    forbNextMdl = new HashMap<Model, Object> ();
	    reqPreviousMdl = new HashMap<Model, Object> ();
	    reqNextMdl = new HashMap<Model, Object> ();
	    
	    forbPreviousCls = new HashMap<String, Object> ();
	    forbNextCls = new HashMap<String, Object> ();
	    reqPreviousCls = new HashMap<String, Object> ();
	    reqNextCls = new HashMap<String, Object> ();
	   
	    ignMdl = new HashMap<Model, Object> ();
	    ignCls = new HashMap<String, Object> ();
	}

    ////////////////////
    // TokenRestriction implementation
    public void checkRestriction (Token token) throws ParserSemanticException
    {
        // Not dealing with null values
        if (token == null)
            return;
        
        ParserSemanticException ex = new ParserSemanticException ("Token did not meet neighboring tokens requirements");
        
        if (hasRequiredNext ())
        {
            boolean valid = validNeighbor (new ForwardTokenIterator (token), reqNextMdl, reqNextCls);
            if (!valid)
                throw ex;
        }
        
        if (hasRequiredPrevious ())
        {
            boolean valid = validNeighbor (new BackwardTokenIterator (token), reqPreviousMdl, reqPreviousCls);
            if (!valid)
                throw ex;
        }
        
        if (hasForbiddenNext ())
        {
            boolean valid = validNeighbor (new ForwardTokenIterator (token), forbNextMdl, forbNextCls);
            if (!valid)
                throw ex;
        }
        
        if (hasForbiddenPrevious ())
        {
            boolean valid = validNeighbor (new BackwardTokenIterator (token), forbPreviousMdl, forbPreviousCls);
            if (!valid)
                throw ex;
        }
    }
	
    ////////////////////
    // Public methods
    public NeighborTokenRestriction addForbiddenNext (Model... models)
    {
        for (Model model : models)
            forbNextMdl.put (model, new Object ());
        
        return this;
    }
    
    public NeighborTokenRestriction addForbiddenNext (String... clsses)
    {
        for (String cls : clsses)
            if (cls != null)
                forbNextCls.put (cls, new Object ());
        
        return this;
    }
	
    public NeighborTokenRestriction addRequiredNext (Model... models)
    {
        for (Model model : models)
            reqNextMdl.put (model, new Object ());
		
        return this;
    }
    
    public NeighborTokenRestriction addRequiredNext (String... clsses)
    {
        for (String cls : clsses)
            if (cls != null)
                reqNextCls.put (cls, new Object ());
        
        return this;
    }
	
    public NeighborTokenRestriction addForbiddenPrevious (Model... models)
    {
        for (Model model : models)
            forbPreviousMdl.put (model, new Object ());
		
        return this;
    }
    
    public NeighborTokenRestriction addForbiddenPrevious (String... clsses)
    {
        for (String cls : clsses)
            if (cls != null)
                forbPreviousCls.put (cls, new Object ());
        
        return this;
    }
	
    public NeighborTokenRestriction addRequiredPrevious (Model... models)
    {
        for (Model model : models)
            reqPreviousMdl.put (model, new Object ());
		
        return this;
    }
    
    public NeighborTokenRestriction addRequiredPrevious (String... clsses)
    {
        for (String cls : clsses)
            if (cls != null)
                reqPreviousCls.put (cls, new Object ());
        
        return this;
    }
    
    public NeighborTokenRestriction addIgnored (Model... models)
    {
        for (Model model : models)
            ignMdl.put (model, new Object ());
        
        return this;
    }
    
    public NeighborTokenRestriction addIgnored (String... clsses)
    {
        for (String cls : clsses)
            if (cls != null)
                ignCls.put (cls, new Object ());
        
        return this;
    }
    
    
    ////////////////////
    // Private methods
    private boolean validNeighbor (TokenIterator iterator, HashMap<Model, Object> mdlTable, HashMap<String, Object> clsTable)
    {
        // Check if we have data for models and for classes
        boolean hasModels = !mdlTable.isEmpty ();
        boolean hasClasses = !clsTable.isEmpty ();
        boolean hasIgnModels = !ignMdl.isEmpty ();
        boolean hasIgnClasses = !ignCls.isEmpty ();
        
        Token curTok = iterator.current ();
        while (true)
        {
            curTok = iterator.next ();
            
            // If we've reached the end of the list, we only have to look into the Model table
            if (curTok == null)
                return (hasModels && mdlTable.get (null) != null);
            
            // Get current Model and init classes holder
            Model curModel = curTok.getModel ();
            Iterator<String> curClasses;
    
            // Check if our current Model matches one of the table's records
            if (hasModels)
                if (mdlTable.get (curModel) != null)
                    return true;
            
            // Check if our current classes match one of the table's records
            if (hasClasses)
            {
                curClasses = curModel.getClasses ();
                while (curClasses.hasNext ())
                {
                    String cls = curClasses.next ();
                    if (clsTable.get(cls) != null)
                        return true;
                }
            }
            
            // Check if our current Model is to be ignored
            if (hasIgnModels)
                if (ignMdl.get (curModel) != null)
                    continue;
            
            // Check if our current classes are to be ignored
            if (hasIgnClasses)
            {
                curClasses = curModel.getClasses ();
                boolean found = false;
                while (curClasses.hasNext ())
                {
                    String cls = curClasses.next ();
                    if (ignCls.get(cls) != null)
                    {
                        found = true;
                        break;
                    }
                }
                
                if (found)
                    continue;
            }
            
            // At this point, we've run into a token that isn't part of that we're looking for
            break;
        }
        
        return false;
    }
    
    private boolean hasRequiredPrevious ()
    {
        return !(reqPreviousMdl.isEmpty () && reqPreviousCls.isEmpty ());
    }
    
    private boolean hasRequiredNext ()
    {
        return !(reqNextMdl.isEmpty () && reqNextCls.isEmpty ());
    }
    
    private boolean hasForbiddenPrevious ()
    {
        return !(forbPreviousMdl.isEmpty () && forbPreviousCls.isEmpty ());
    }
    
    private boolean hasForbiddenNext ()
    {
        return !(forbNextMdl.isEmpty () && forbNextCls.isEmpty ());
    }
	
}
